The Madison Open Data portal has property tax information by parcel: https://data-cityofmadison.opendata.arcgis.com/datasets/tax-parcels-assessor-property-information/data
Data was manually downloaded as a shapefile and then saved as an rds object for faster loading.
library(tidyverse)
## -- Attaching packages --------------------------------------------------------- tidyverse 1.3.0 --
## v ggplot2 3.3.2 v purrr 0.3.4
## v tibble 3.0.3 v dplyr 1.0.2
## v tidyr 1.1.2 v stringr 1.4.0
## v readr 1.3.1 v forcats 0.5.0
## -- Conflicts ------------------------------------------------------------ tidyverse_conflicts() --
## x dplyr::filter() masks stats::filter()
## x dplyr::lag() masks stats::lag()
library(sf)
## Linking to GEOS 3.8.0, GDAL 3.0.4, PROJ 6.3.1
library(ggplot2)
The dataset is large, with over 80,000 observations and 144 variables. It does not contain information on ownership.
parcels <- readRDS("data/parcels.rds")
str(parcels)
## tibble [80,158 x 144] (S3: sf/tbl_df/tbl/data.frame)
## $ OBJECTID : int [1:80158] 1 2 3 4 5 6 7 8 9 10 ...
## $ Parcel : chr [1:80158] "060801101019" "060801101027" "060801101035" "060801101043" ...
## $ XRefParcel: chr [1:80158] "060801101019" "060801101027" "060801101035" "060801101043" ...
## $ Address : chr [1:80158] "2001 Rae Ln" "2005 Rae Ln" "2009 Rae Ln" "2013 Rae Ln" ...
## $ DateParcel: Date[1:80158], format: "2020-06-03" "2020-06-03" ...
## $ PropertyCl: chr [1:80158] "Residential" "Residential" "Residential" "Residential" ...
## $ PropertyUs: chr [1:80158] "Single family" "Single family" "Single family" "Single family" ...
## $ Assessment: int [1:80158] 1 1 1 1 1 1 1 1 1 1 ...
## $ AreaName : chr [1:80158] "Meadowood" "Meadowood" "Meadowood" "Meadowood" ...
## $ MoreThanOn: chr [1:80158] NA NA NA NA ...
## $ HomeStyle : chr [1:80158] "Ranch" "Ranch" "Ranch" "Ranch" ...
## $ YearBuilt : int [1:80158] 1960 1959 1959 1962 1959 1962 1964 1965 1958 1959 ...
## $ Bedrooms : int [1:80158] 3 4 3 3 3 5 5 4 3 4 ...
## $ FullBaths : int [1:80158] 1 1 1 2 2 2 2 2 1 2 ...
## $ HalfBaths : int [1:80158] 2 1 1 1 0 0 0 0 1 0 ...
## $ TotalLivin: int [1:80158] 1371 1488 1290 1043 1386 1008 990 1076 1208 1603 ...
## $ FirstFloor: int [1:80158] 1371 1488 1290 1043 1386 1008 990 1076 1208 1603 ...
## $ SecondFloo: int [1:80158] 0 0 0 0 0 0 0 0 0 0 ...
## $ ThirdFloor: int [1:80158] 0 0 0 0 0 0 0 0 0 0 ...
## $ AboveThird: int [1:80158] 0 0 0 0 0 0 0 0 0 0 ...
## $ FinishedAt: int [1:80158] 0 0 0 0 0 0 0 0 0 0 ...
## $ Basement : int [1:80158] 1254 1008 1066 1008 1386 1008 935 1040 1208 1120 ...
## $ FinishedBa: int [1:80158] 686 350 564 607 550 750 637 860 380 560 ...
## $ ExteriorWa: chr [1:80158] "Wood" "Wood" "Aluminum/Vinyl" "Wood" ...
## $ Exterior_1: chr [1:80158] NA NA NA NA ...
## $ Fireplaces: int [1:80158] 1 1 0 0 1 1 1 0 0 1 ...
## $ CentralAir: chr [1:80158] "YES" "YES" "YES" "YES" ...
## $ PartialAss: chr [1:80158] NA NA NA NA ...
## $ AssessedBy: chr [1:80158] NA NA NA NA ...
## $ CurrentLan: int [1:80158] 70000 74800 79100 76000 71100 64700 73900 66000 74000 70600 ...
## $ CurrentImp: int [1:80158] 157400 152700 145500 170100 167400 148100 183600 180100 151500 165600 ...
## $ CurrentTot: int [1:80158] 227400 227500 224600 246100 238500 212800 257500 246100 225500 236200 ...
## $ PreviousLa: int [1:80158] 66000 70600 74600 71700 67100 61000 69700 62300 69800 66600 ...
## $ PreviousIm: int [1:80158] 148500 144000 137300 160500 157900 139800 173200 169900 142900 156200 ...
## $ PreviousTo: int [1:80158] 214500 214600 211900 232200 225000 200800 242900 232200 212700 222800 ...
## $ NetTaxes : num [1:80158] 4541 4543 4482 4940 4778 ...
## $ SpecialAss: num [1:80158] 0 0 0 0 0 0 0 0 0 0 ...
## $ OtherCharg: num [1:80158] 0 0 678 0 0 ...
## $ TotalTaxes: num [1:80158] 4541 4543 5160 4940 4778 ...
## $ LotSize : num [1:80158] 14270 14718 18867 14984 13334 ...
## $ Zoning1 : chr [1:80158] "SR-C1" "SR-C1" "SR-C1" "SR-C1" ...
## $ Zoning2 : chr [1:80158] NA NA NA NA ...
## $ Zoning3 : chr [1:80158] NA NA NA NA ...
## $ Zoning4 : chr [1:80158] NA NA NA NA ...
## $ FrontageSt: chr [1:80158] "Rae Ln" "Rae Ln" "Rae Ln" "Rae Ln" ...
## $ WaterFront: chr [1:80158] "NO" "NO" "NO" "NO" ...
## $ TIFDistric: int [1:80158] 0 0 0 0 0 0 0 0 0 0 ...
## $ TaxSchoolD: chr [1:80158] NA NA NA NA ...
## $ Attendance: chr [1:80158] "Madison" "Madison" "Madison" "Madison" ...
## $ Elementary: chr [1:80158] "Huegel" "Huegel" "Huegel" "Huegel" ...
## $ MiddleScho: chr [1:80158] "Toki" "Toki" "Toki" "Toki" ...
## $ HighSchool: chr [1:80158] "Memorial" "Memorial" "Memorial" "Memorial" ...
## $ Ward : int [1:80158] 95 95 95 95 95 95 95 95 95 95 ...
## $ StateAssem: int [1:80158] 78 78 78 78 78 78 78 78 78 78 ...
## $ RefuseDist: chr [1:80158] "04B" "04B" "04B" "04B" ...
## $ RefuseURL : chr [1:80158] "http://www.cityofmadison.com/streets/documents/tueB.pdf" "http://www.cityofmadison.com/streets/documents/tueB.pdf" "http://www.cityofmadison.com/streets/documents/tueB.pdf" "http://www.cityofmadison.com/streets/documents/tueB.pdf" ...
## $ Previous_1: int [1:80158] 61700 66000 69700 67000 62700 57000 65100 58200 65200 62200 ...
## $ Previous_2: int [1:80158] 145500 134600 128300 150000 150600 130700 161900 158800 133600 146000 ...
## $ Previous_3: int [1:80158] 207200 200600 198000 217000 213300 187700 227000 217000 198800 208200 ...
## $ AlderDistr: int [1:80158] 20 20 20 20 20 20 20 20 20 20 ...
## $ Assessme_1: Date[1:80158], format: "2020-06-03" "2020-06-03" ...
## $ BlockNumbe: int [1:80158] 0 0 0 0 0 0 0 0 0 0 ...
## $ BuildingDi: int [1:80158] 2 2 2 2 2 2 2 2 2 2 ...
## $ CapitolFir: chr [1:80158] "-" "-" "-" "-" ...
## $ Conditiona: int [1:80158] 0 0 0 0 0 0 0 0 0 0 ...
## $ CouncilHol: int [1:80158] 0 0 0 0 0 0 0 0 0 0 ...
## $ DateAdded : Date[1:80158], format: "1970-01-01" "1970-01-01" ...
## $ DeedPage : int [1:80158] 0 0 0 0 0 0 0 0 0 0 ...
## $ DeedRestri: int [1:80158] 0 0 0 0 0 0 0 0 0 0 ...
## $ DeedVolume: int [1:80158] 0 0 0 0 0 0 0 0 0 0 ...
## $ Electrical: int [1:80158] 1 1 1 1 1 1 1 1 1 1 ...
## $ EnvHealthD: int [1:80158] 31 31 31 31 31 31 31 31 31 31 ...
## $ ExemptionT: chr [1:80158] "-" "-" "-" "-" ...
## $ FireDistri: int [1:80158] 1 1 1 1 1 1 1 1 1 1 ...
## $ FloodPlain: int [1:80158] 0 0 0 0 0 0 0 0 0 0 ...
## $ FuelStorag: int [1:80158] 0 0 0 0 0 0 0 0 0 0 ...
## $ HeatingDis: int [1:80158] 2 2 2 2 2 2 2 2 2 2 ...
## $ Holds : chr [1:80158] NA NA NA NA ...
## $ IllegalLan: int [1:80158] 0 0 0 0 0 0 0 0 0 0 ...
## $ LandfillPr: int [1:80158] 0 0 0 0 0 0 0 0 0 0 ...
## $ LandfillRe: chr [1:80158] NA NA NA NA ...
## $ Landmark : chr [1:80158] NA NA NA NA ...
## $ LandscapeB: int [1:80158] 0 0 0 0 0 0 0 0 0 0 ...
## $ LocalHisto: chr [1:80158] "-" "-" "-" "-" ...
## $ LotDepth : num [1:80158] 0 0 0 0 0 0 0 0 150 0 ...
## $ LotNumber : int [1:80158] 0 0 0 0 0 0 0 0 0 0 ...
## $ LotteryCre: int [1:80158] 0 0 0 0 0 0 0 0 0 0 ...
## $ LotType1 : chr [1:80158] "1 - Regular" "1 - Regular" "2 - Irregular" "2 - Irregular" ...
## $ LotType2 : chr [1:80158] "1 - Corner" "0 - No Exception" "0 - No Exception" "0 - No Exception" ...
## $ LotWidth : num [1:80158] 0 0 0 0 0 0 0 0 93 0 ...
## $ MCDCode : chr [1:80158] "MADC" "MADC" "MADC" "MADC" ...
## $ NationalHi: int [1:80158] 0 0 0 0 0 0 0 0 0 0 ...
## $ Neighborho: chr [1:80158] "No description entered" "No description entered" "No description entered" "No description entered" ...
## $ Neighbor_1: chr [1:80158] "0 - No description entered" "0 - No description entered" "0 - No description entered" "0 - No description entered" ...
## $ Neighbor_2: chr [1:80158] "0 - No description entered" "0 - No description entered" "0 - No description entered" "0 - No description entered" ...
## $ Neighbor_3: chr [1:80158] "0 -" "0 -" "0 -" "0 -" ...
## $ NoiseAirpo: int [1:80158] 0 0 0 0 0 0 0 0 0 0 ...
## $ NoiseRailr: int [1:80158] 0 0 0 0 0 0 0 0 0 0 ...
## $ NoiseStree: int [1:80158] 61 61 61 61 61 0 0 0 0 0 ...
## [list output truncated]
## - attr(*, "sf_column")= chr "geometry"
## - attr(*, "agr")= Factor w/ 3 levels "constant","aggregate",..: NA NA NA NA NA NA NA NA NA NA ...
## ..- attr(*, "names")= chr [1:143] "OBJECTID" "Parcel" "XRefParcel" "Address" ...
I don’t see a data dictionary, but most of the variable names are self-explanatory. Some interesting stuff: - There are variables for noise from streets, railroads, and the airport. But it’s unclear what the values mean. I initially thought they were decibels from noise measurements, but the values are other 0, or 60 (airport), 61 (street, railroad)
parcels %>%
filter(NoiseRailr > 0) %>%
select(Address, NoiseRailr) %>%
arrange(desc(NoiseRailr))
## Simple feature collection with 2121 features and 2 fields
## geometry type: POLYGON
## dimension: XY
## bbox: xmin: -89.49105 ymin: 43.03158 xmax: -89.26573 ymax: 43.1533
## geographic CRS: WGS 84
## # A tibble: 2,121 x 3
## Address NoiseRailr geometry
## <chr> <int> <POLYGON [°]>
## 1 5905 Old Midd~ 61 ((-89.48142 43.08084, -89.48215 43.08132, -89.4817~
## 2 5911 Old Midd~ 61 ((-89.48253 43.08156, -89.4824 43.08161, -89.48191~
## 3 5917 Old Midd~ 61 ((-89.48253 43.08156, -89.48283 43.08175, -89.4827~
## 4 1509 Capital ~ 61 ((-89.48282 43.08222, -89.48275 43.08222, -89.4826~
## 5 1505 Capital ~ 61 ((-89.48283 43.08175, -89.48365 43.08227, -89.4830~
## 6 1500 Capital ~ 61 ((-89.48418 43.08262, -89.48371 43.08279, -89.4831~
## 7 1504 Capital ~ 61 ((-89.48302 43.08305, -89.48302 43.08305, -89.4828~
## 8 9 Veblen Pl 61 ((-89.48302 43.08305, -89.48371 43.08279, -89.4838~
## 9 13 Veblen Pl 61 ((-89.48418 43.08262, -89.48473 43.08297, -89.4841~
## 10 17 Veblen Pl 61 ((-89.48503 43.08317, -89.4842 43.08342, -89.48415~
## # ... with 2,111 more rows
unique(parcels$HomeStyle)
## [1] "Ranch" "Bi-level"
## [3] "Modern two story" "Colonial"
## [5] "Townhouse" NA
## [7] "Split-level" "Contemporary"
## [9] "Cape Cod" "Townhouse Duplex"
## [11] "Tudor" "Apartment"
## [13] "Garden" "Ranch Duplex"
## [15] "Townhouse End Unit" "Old Style"
## [17] "Cottage" "Garden with loft"
## [19] "Bungalow" "Flat"
## [21] "Victorian georgian regency" "Spanish mediterranean"
## [23] "New style modern international" "Mansion"
## [25] "Ranch End Unit" "Converted"
## [27] "Hi-rise" "Other"
## [29] "Rowhouse"
Add a new variable for NetTaxes per sq ft.
parcels2 <- parcels %>%
mutate(tax_per_sqft = NetTaxes / LotSize)
parcels2 %>%
mutate(Zoning1 = fct_reorder(Zoning1, tax_per_sqft, median)) %>%
group_by(Zoning1) %>%
ggplot(aes(Zoning1, tax_per_sqft)) +
geom_boxplot() +
scale_y_log10() +
coord_flip()
## Warning: Transformation introduced infinite values in continuous y-axis
## Warning: Removed 21291 rows containing non-finite values (stat_boxplot).
Unfortunately, the
Zoning1 field doesn’t always contain the current zoning code. For instance, there are many codes starting with HIS-. I firstx thought these may represent hstorical zoning codes, but it looks like they are the city’s designated historic districts. https://www.cityofmadison.com/dpced/planning/local-historic-districts/1601/ In most cases the actual zoning district then is in the Zoning2 field, and in a few cases, you have to go to Zoning3.
parcels3 <- parcels2 %>%
mutate(zoning_clean = case_when(str_detect(Zoning1, "HIS-") & !str_detect(Zoning2, "HIS-") ~ Zoning2,
str_detect(Zoning1, "HIS-") & str_detect(Zoning2, "HIS-") ~ Zoning3,
TRUE ~ Zoning1))
library(mapdeck)
##
## Attaching package: 'mapdeck'
## The following object is masked from 'package:tibble':
##
## add_column
mapdeck(style = 'mapbox://styles/mapbox/dark-v9', pitch = 45) %>%
add_polygon(parcels3, elevation = "tax_per_sqft",
elevation_scale = 1000000)
## Registered S3 method overwritten by 'jsonify':
## method from
## print.json jsonlite